package org.gap.jseed;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import javassist.ClassPool;

import org.gap.jseed.annotation.Bean;
import org.gap.jseed.annotation.BeanInvocationHandler;
import org.gap.jseed.annotation.Factory;
import org.gap.jseed.annotation.FactoryMethodHandler;
import org.gap.jseed.annotation.Inject;
import org.gap.jseed.annotation.InjectHandler;
import org.gap.jseed.annotation.Log;
import org.gap.jseed.annotation.LoggingHandler;
import org.gap.jseed.annotation.validation.Nullable;
import org.gap.jseed.annotation.validation.NullableHandler;

@SuppressWarnings("unchecked")
public class Seed {
	private static Map<Class<?>, Class<?>> concreteClasses;
	private static Injector injector;
	
	static {
		concreteClasses = new HashMap<Class<?>, Class<?>>();
		injector = new Injector();
		Module module = new Module() {
			@Override
			public void defineInjections(Configuration injector) {
				injector.registerClass(Bean.class, BeanInvocationHandler.class);
				
				injector.registerMethod(Factory.class, FactoryMethodHandler.class);
				injector.registerMethod(Log.class, LoggingHandler.class);
				injector.registerMethod(Nullable.class, NullableHandler.class);
				
				injector.registerField(Inject.class, InjectHandler.class);
			}
		};
		module.defineInjections(injector);
	}
	
	/**
	 * Used to instantiate a the type passed in.
	 * @param <T> class type the instance is sub-classing
	 * @param type the definition to be seeded
	 * @return
	 */
	public static <T> Class<T> grow(Class<T> seed) {
		return grow(null, seed);
	}
	
	public static <T> Class<T> grow(Class<? extends Annotation> annotation, Class<T> seed) {
		if (!concreteClasses.containsKey(seed)) {
			try {
				Class<? extends T> grown = injector.inject(annotation, seed);
				for (@SuppressWarnings("rawtypes") Constructor each : grown.getConstructors()) {
					each.setAccessible(true);
					each.newInstance(new Object[0]);
				}
				concreteClasses.put(seed, grown);
			} catch (Exception e) {
				e.printStackTrace();
				throw new IllegalStateException("Unable to inject " + seed + " " + e.getMessage(), e);
			} 
		}		
		return (Class<T>)concreteClasses.get(seed);
	}
	
	public static <T> T getInstance(Class<T> seed) {
		Class<T> plant = grow(seed);
		try {
			return plant.newInstance();
		} catch (Exception e) {
			throw new IllegalStateException("Unable to instantiate seed " + seed.getName(), e);
		}
	}
	
	public static void loadInjector(Module...modules) {
		for (Module module : modules) {
			module.defineInjections(injector);
		}
	}
	
	public static void addImport(String packageName) {
		ClassPool.getDefault().importPackage(packageName);
	}
	
	public static void clean() {
		injector = new Injector();
		concreteClasses.clear();
	}
}
